package org.vacoor.nothing.misc.express;

import com.google.common.base.Charsets;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vacoor.nothing.common.util.HttpUtils;
import org.vacoor.nothing.common.util.IOUtils;
import org.vacoor.nothing.common.util.StringUtils;
import org.vacoor.nothing.common.xml.JAXB2;
import org.vacoor.nothing.misc.express.model.TrackEvent;
import org.vacoor.nothing.misc.express.model.TrackInfo;
import org.vacoor.nothing.misc.express.usps.api.UspsRequest;
import org.vacoor.nothing.misc.express.usps.api.data.UspsAddress;
import org.vacoor.nothing.misc.express.usps.api.track.field.TrackFieldEvent;
import org.vacoor.nothing.misc.express.usps.api.track.field.TrackFieldId;
import org.vacoor.nothing.misc.express.usps.api.track.field.TrackFieldInfo;
import org.vacoor.nothing.misc.express.usps.api.track.field.TrackFieldRequest;

import java.io.InputStream;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
 * USPS 查询服务
 *
 * @author vacoor
 */
public class Uspsx {
    private static final Logger LOG = LoggerFactory.getLogger(Uspsx.class);
    /*-
     * 测试: http://production.shippingapis.com/ShippingAPITest.dll?API=RateV4&XML=
     * 正式: http://production.shippingapis.com/ShippingAPI.dll?API=RateV4&XML=
     */
    private static final String DEFAULT_SERVICE_URL = "http://production.shippingapis.com/ShippingAPI.dll";
    private static final String DEFAULT_ADDR_VERITY_API = "Verify";
    private static final String DEFAULT_ZIP_CODE_LOOKUP_API = "ZipCodeLookup";
    private static final String DEFAULT_CITY_STATE_LOOKUP_API = "CityStateLookup";
    private static final String DEFAULT_TRACK_API = "TrackV2";
    private static final String DEFAULT_RATE_API = "RateV4";

    private String serviceUrl = DEFAULT_SERVICE_URL;                      // 服务地址
    private String addrValidationApi = DEFAULT_ADDR_VERITY_API;         // 地址验证 API 名称
    private String zipCodeLookupApi = DEFAULT_ZIP_CODE_LOOKUP_API;      // 城市/州 -> 邮编查找
    private String cityStateLookupApi = DEFAULT_CITY_STATE_LOOKUP_API;  // 邮编 -> 城市/州查找
    private String trackApi = DEFAULT_TRACK_API;                            // 物流追踪
    private String rateApi = DEFAULT_RATE_API;                              // 费用计算

    private String userId;
    private String password;
    private int timeout = 10 * 1000;

    //
    public static Uspsx create(String userId, String password) {
        Uspsx usps = new Uspsx();
        usps.userId = userId;
        usps.password = password;

        return usps;
    }

    private Uspsx() {
    }


    /*
    // 根据给定的地址查找邮编 (ZIP Code Lookup)
    public Result<ZipCodeLookupResponse> lookupZipCode(Address... addrs) {
        ZipCodeLookupRequest req = ZipCodeLookupRequest.of(addrs);

        return callUspsApi(zipCodeLookupApi, req, ZipCodeLookupResponse.class);
    }

    // 根据给定的邮编查找城市/州(City/State Lookup)
    public Result<CityStateLookupResponse> lookupCityState(ZipCode... zipCodes) {
        CityStateLookupRequest req = CityStateLookupRequest.of(zipCodes);

        return callUspsApi(cityStateLookupApi, req, CityStateLookupResponse.class);
    }
    */

    /**
     * 简单物流追踪
     */
    /*
    public org.vacoor.nothing.misc.express.Result<TrackResponse> track(String trackingNo) {
        TrackRequest req = TrackRequest.of(trackingNo);

        Result<TrackResponse> result = callUspsApi(trackApi, req, TrackResponse.class);
    }
    */

    /**
     * 详细物流追踪
     */
    public TrackInfo track(String trackingNo) {
        return track(new String[]{trackingNo}).get(0);
    }

    public List<TrackInfo> track(String... trackingNos) {
        TrackFieldRequest req = TrackFieldRequest.create();
        // revision = 1 才会响应 event code
        // req.setRevision("1");
        // 下面好像没有太大作用，但是必须指定
        req.setClientIp("127.0.0.1");
        req.setSourceId("Track Field Request");

        List<TrackFieldId> ids = Lists.<TrackFieldId>newArrayList();
        for (String trackingNo : trackingNos) {
            ids.add(new TrackFieldId(trackingNo));
        }
        req.setTrackFieldIds(ids);
//        return trackField(req);
        return null;
    }


    /**
     * 调用 USPS API
     *
     * @param api
     * @param req
     * @return
     */
    public String callUspsApi(String api, UspsRequest req) {
        req.setUserId(userId);
        req.setPassword(password);

        Map<String, String> data = ImmutableMap.of("API", api, "XML", JAXB2.marshal(req));

        if (LOG.isDebugEnabled()) {
            LOG.debug("invoke usps api, url: {}, xml: {}", serviceUrl, data);
        }

        try {
            InputStream is = HttpUtils.post(serviceUrl, data, timeout);
            if (null != is) {
                String ret = IOUtils.readText(is, Charsets.UTF_8, true);

                if (LOG.isDebugEnabled()) {
                    LOG.debug("USPS Response: {}", ret);
                }

                return ret;
            }

            throw new IllegalStateException("invoke usps api fail, not response");
        } catch (Exception e) {
            throw new IllegalStateException("invoke usps api fail", e);
        }
    }

    /* ***************************
     *
     * ***************************/

    /**
     * 转换地址信息
     */
    protected UspsAddress transform(org.vacoor.nothing.misc.express.model.Address addr) {
        UspsAddress uspsAddr = new UspsAddress();
        uspsAddr.setId(addr.getId());
        String[] streets = addr.getStreets();
        if (null != streets) {
            if (streets.length > 0) {
                uspsAddr.setAddress2(streets[0]);
                if (streets.length > 1) {
                    uspsAddr.setAddress1(streets[1]);
                }
            }
        }
        uspsAddr.setCity(StringUtils.trimToEmpty(addr.getCity()));
        uspsAddr.setState(StringUtils.trimToEmpty(addr.getState()));
        uspsAddr.setZip5(StringUtils.trimToEmpty(addr.getZip()));
        uspsAddr.setFirmName(StringUtils.trimToEmpty(addr.getName()));

        return uspsAddr;
    }

    /**
     * 转换地址信息
     */
    protected org.vacoor.nothing.misc.express.model.Address transform(UspsAddress uspsAddr) {
        org.vacoor.nothing.misc.express.model.Address addr = new org.vacoor.nothing.misc.express.model.Address();
        addr.setId(uspsAddr.getId());
        List<String> streets = Lists.newArrayList();
        String addr2 = uspsAddr.getAddr2();
        String addr1 = uspsAddr.getAddr1();
        if (StringUtils.hasText(addr2)) {
            streets.add(addr2);
        }
        if (StringUtils.hasText(addr1)) {
            streets.add(addr1);
        }
        addr.setStreets(streets.toArray(new String[streets.size()]));
        addr.setCity(uspsAddr.getCity());
        addr.setState(uspsAddr.getState());
        addr.setName(uspsAddr.getFirmName());
        addr.setZip(uspsAddr.getZip5());

//        addr.setMessage(getMessage(uspsAddr.getErr()));
        return addr;
    }

    /**
     * 转换追踪信息
     */
    protected org.vacoor.nothing.misc.express.model.TrackInfo transform(TrackFieldInfo uspsInfo) {
            /*-
              03: Acceptance 验收
              SF: Departed Post Office              邮局出发?
              EF: Departed USPS Facility            USPS 派发?
              10: Arrived at USPS Origin Facility   抵达 USPS 产地?
              DD: Processed Through Sort Facility   处理分拣处??
              BB: Arrived at Sort Facility          到达分拣处?
              D0: Processed Through Sort Facility   处理分拣处??
              H0: Attempted Delivery Abroad         尝试国外交付?
              I0: Delivered                         已签收
             */
        org.vacoor.nothing.misc.express.model.TrackInfo info = new org.vacoor.nothing.misc.express.model.TrackInfo();
        info.setTrackingNo(uspsInfo.getId());
//        info.setMessage(getMessage(uspsInfo.getErr()));
        // TODO INFO STATE
        List<TrackFieldEvent> details = uspsInfo.getTrackDetails();
        details.add(0, uspsInfo.getTrackSummary());

        List<TrackEvent> events = Lists.transform(details, new Function<TrackFieldEvent, TrackEvent>() {
            @Override
            public TrackEvent apply(TrackFieldEvent input) {
                return transform(input);
            }
        });
        info.setDetails(events);

        return info;
    }

    /**
     * 转换追踪事件
     */
    protected org.vacoor.nothing.misc.express.model.TrackEvent transform(TrackFieldEvent uspsEvt) {
        org.vacoor.nothing.misc.express.model.TrackEvent event = new org.vacoor.nothing.misc.express.model.TrackEvent();

        String date = null;
        try {
            DateFormat fmt = new SimpleDateFormat("MMMMM dd, yyyy h:mm decode", Locale.US);
            date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(fmt.parse(uspsEvt.getEventDate() + " " + uspsEvt.getEventTime()));
        } catch (ParseException e) {
            throw new IllegalStateException(e);
        }

        StringBuilder location = new StringBuilder();
        int index = -1;

        String[] addrs = new String[]{
                uspsEvt.getEventCountry(),
                uspsEvt.getEventState(),
                uspsEvt.getEventCity()
        };

        for (String addr : addrs) {
            if (null == addr) {
                continue;
            }
            int l = addr.length();
            if (l > 0 && index > -1 && location.length() > index) {
                location.append(" ");
                index += 1;
            }
            location.append(addr);
            index += l;
        }

        String sz = uspsEvt.getEventState() + uspsEvt.getEventZipCode();
        if (null != sz && sz.length() > 0 && location.length() > index) {
            location.append(", ").append(sz);
        }

        event.setDatetime(date);
        event.setLocation(location.toString());
        event.setMessage(uspsEvt.getEvent());

        return event;
    }

    public String getServiceUrl() {
        return serviceUrl;
    }

    public void setServiceUrl(String serviceUrl) {
        this.serviceUrl = serviceUrl;
    }
}
